import { execSync, spawnSync } from "child_process";
import { Logger } from "../utils/Logger.js";
import path from "path";
import { getProjectOpsFileV2 } from "../utils/OpsFile.js";
import process from "process";
import {FeishuBot} from "../utils/BotClient.js";
import {OPSConstant} from "../constant/OPSConstant.js";

const ZERO_SHA_PATTERN = /^0+$/;
const DEFAULT_CHAT_ID = process.env.FEISHU_PUSH_CHAT_ID || OPSConstant.OPS_CHAT_ID;
const DEFAULT_INTRO_TEXT = "[N]ew [B]uild 脚本更新通知";
const DEFAULT_FOOTER_TEXT = "使用任意指令，即可自动更新";
const REPO_PUSH_CONFIG = {
    "nikou-node": {
        introText: "[N]ew [B]uild 脚本更新通知"
    },
    "chrome-nicole": {
        introText: "nikou[妮蔻]Edge-浏览器插件更新通知"
    },
    "chrome-nicole-v2": {
        introText: "nikou[妮蔻]Chrome-浏览器插件更新通知"
    }
};
const NO_PUSH_PATTERN = /no-push/i;

function isZeroSha(sha = "") {
    return !sha || ZERO_SHA_PATTERN.test(sha);
}

function normalizeRef(ref = "") {
    return ref.replace(/^refs\/(heads|remotes|tags)\//, "") || ref;
}

async function readPrePushUpdatesFromStdin() {
    if (typeof process.stdin.isTTY === "boolean" && process.stdin.isTTY) {
        return [];
    }
    if (process.stdin.readableEnded) {
        return [];
    }
    process.stdin.setEncoding("utf8");
    return new Promise((resolve) => {
        const chunks = [];
        const onData = chunk => chunks.push(chunk);
        const onEnd = () => {
            process.stdin.off("data", onData);
            process.stdin.off("error", onError);
            process.stdin.pause();
            const raw = chunks.join("").trim();
            if (!raw) {
                resolve([]);
                return;
            }
            const updates = raw.split(/\r?\n/)
                .map(line => line.trim())
                .filter(line => !!line)
                .map(line => {
                    const [localRef = "", localSha = "", remoteRef = "", remoteSha = ""] = line.split(/\s+/);
                    return { localRef, localSha, remoteRef, remoteSha };
                });
            resolve(updates);
        };
        const onError = () => {
            process.stdin.off("data", onData);
            process.stdin.off("end", onEnd);
            process.stdin.pause();
            resolve([]);
        };
        process.stdin.on("data", onData);
        process.stdin.once("end", onEnd);
        process.stdin.once("error", onError);
        process.stdin.resume();
    });
}

/**
 * AutoMerge 命令处理器
 */
class AutoMergeCommandHandler {
    constructor() {
        this.logger = new Logger("AutoMerge");
        try {
            this.projectOpsFile = getProjectOpsFileV2();
        } catch (error) {
            if (error && typeof error.message === "string" && error.message.includes("项目配置文件不存在")) {
                this.projectOpsFile = null;
                this.logger.debug("未检测到 ops.conf，自动合并流程跳过。");
            } else {
                throw error;
            }
        }
    }

    /**
     * 执行命令解析
     */
    async execute(args) {
        await this.trySendNikouPushDigest(args);
        if (!this.projectOpsFile) {
            return;
        }
        try {
            const repoPath = process.cwd();
            const url = execSync("git config --get remote.origin.url").toString().trim();
            const repoName = path.basename(url, ".git");
            const currentBranch = execSync("git rev-parse --abbrev-ref HEAD", { cwd: repoPath })
                .toString().trim();
            const latestHash = execSync("git rev-parse HEAD", { cwd: repoPath })
                .toString().trim();

            this.logger.info(`当前分支: ${currentBranch}, 最新提交: ${latestHash}`);
            const targetBranch = await this.projectOpsFile.read(`git_merge.auto_merge_${repoName}_${currentBranch}`);
            if (!targetBranch) {
                this.logger.warn(`未找到 ${currentBranch} 对应的配置分支，跳过自动合并。`);
                return;
            }

            this.logger.info(`检测到配置分支: ${targetBranch}，准备执行合并。`);

            // 使用 worktree 临时目录合并，避免切换当前分支
            const tmpDir = path.join(repoPath, `.git/automerge-${targetBranch}-${Date.now()}`);
            this.runGit(["worktree", "add", tmpDir, targetBranch], { cwd: repoPath });

            try {
                // 先拉取远程目标分支的最新提交
                this.runGit(["fetch", "origin", targetBranch], { cwd: tmpDir });
                this.runGit(["reset", "--hard", `origin/${targetBranch}`], { cwd: tmpDir });
                // 再 merge 当前分支到临时 worktree
                this.runGit(["merge", "--no-commit", "--no-ff", currentBranch], {
                    cwd: tmpDir,
                    stdio: "inherit"
                });
                // 如果没有冲突，提交并推送
                const commitMessage = `Auto-merge branch '${currentBranch}' into '${targetBranch}'`;
                const commitResult = this.runGit(
                    ["commit", "-m", commitMessage],
                    { cwd: tmpDir, ignoreError: true }
                );
                if (commitResult.status !== 0) {
                    this.logger.debug("没有新的提交需要推送，跳过 git commit。");
                }
                this.runGit(["push", "origin", targetBranch], {
                    cwd: tmpDir,
                    stdio: "inherit"
                });

                this.logger.success(`成功合并 ${currentBranch} -> ${targetBranch} 并已推送远程。`);
            } catch (err) {
                this.logger.error(`合并失败，可能存在冲突或远程更新，请手动介入。`);
                this.logger.error(err.message);
            } finally {
                // 清理临时 worktree
                this.runGit(["worktree", "remove", tmpDir, "--force"], { cwd: repoPath });
            }
        } catch (err) {
            this.logger.error(`执行失败: ${err.message}`);
        }
    }

    /**
     * 以跨平台方式执行 git 命令
     * @param {string[]} args 子命令及参数
     * @param {{cwd?: string, stdio?: "pipe"|"inherit"|"ignore", ignoreError?: boolean}} options 运行选项
     * @returns {import("child_process").SpawnSyncReturns<string>} 执行结果
     * @private
     */
    runGit(args, options = {}) {
        const { cwd, stdio = "pipe", ignoreError = false } = options;
        const result = spawnSync("git", args, {
            cwd,
            stdio,
            windowsHide: true,
            encoding: "utf-8"
        });

        if (result.error) {
            if (ignoreError) {
                return result;
            }
            throw result.error;
        }

        if (typeof result.status === "number" && result.status !== 0) {
            if (ignoreError) {
                return result;
            }
            const stderrOutput = typeof result.stderr === "string" ? result.stderr : "";
            throw new Error(stderrOutput || `git ${args.join(" ")}`);
        }

        return result;
    }

    async trySendNikouPushDigest(args = []) {
        const repoPath = process.cwd();
        const repoRoot = this.safeGitRoot(repoPath);
        if (!repoRoot) {
            return;
        }
        const repoName = path.basename(repoRoot);
        const repoConfig = this.resolveRepoNotificationConfig(repoName);
        if (!repoConfig) {
            return;
        }
        let updates = [];
        try {
            updates = await readPrePushUpdatesFromStdin();
        } catch (error) {
            this.logger.debug(`读取 pre-push 输入失败: ${error.message}`);
            return;
        }
        if (updates.length === 0) {
            return;
        }
        const commitMessages = this.collectCommitMessages(repoRoot, updates);
        if (commitMessages.length === 0) {
            this.logger.debug("未检测到需要通知的 commit，跳过飞书推送。");
            return;
        }
        const { localRefs, remoteRefs } = this.collectRefSummary(updates);
        const { remoteName, remoteUrl } = this.resolveRemoteInfo(repoRoot, args);
        try {
            const bot = new FeishuBot(repoConfig.chatId);
            await bot.sendCommitDigest({
                commitMessages,
                repoName,
                branch: localRefs.join(", "),
                remoteBranch: remoteRefs.join(", "),
                remoteName,
                remoteUrl,
                introText: repoConfig.introText,
                footerText: repoConfig.footerText
            });
        } catch (error) {
            this.logger.warn(`飞书 push 通知失败: ${error.message}`);
        }
    }

    collectCommitMessages(repoRoot, updates) {
        const commitMap = new Map();
        updates.forEach(({ localSha, remoteSha }) => {
            if (isZeroSha(localSha)) {
                return;
            }
            const range = isZeroSha(remoteSha) ? localSha : `${remoteSha}..${localSha}`;
            const result = spawnSync("git", ["log", "--reverse", "--format=%H%x01%s", range], {
                cwd: repoRoot,
                encoding: "utf-8"
            });
            if (result.status !== 0) {
                const detail = (result.stderr || "").toString().trim();
                this.logger.warn(`解析 commit 日志失败 (${range}): ${detail}`);
                return;
            }
            (result.stdout || "")
                .split("\n")
                .map(line => line.trim())
                .filter(line => !!line)
                .forEach(line => {
                    const [hash, message = ""] = line.split("\x01");
                    if (!hash || commitMap.has(hash)) {
                        return;
                    }
                    commitMap.set(hash, message.trim());
                });
        });
        return Array.from(commitMap.values()).filter(message => !NO_PUSH_PATTERN.test(message));
    }

    collectRefSummary(updates) {
        const localRefs = new Set();
        const remoteRefs = new Set();
        updates.forEach(({ localRef, remoteRef, localSha }) => {
            if (isZeroSha(localSha)) {
                return;
            }
            if (localRef) {
                localRefs.add(normalizeRef(localRef));
            }
            if (remoteRef) {
                remoteRefs.add(normalizeRef(remoteRef));
            }
        });
        return {
            localRefs: Array.from(localRefs),
            remoteRefs: Array.from(remoteRefs)
        };
    }

    resolveRemoteInfo(repoRoot, args = []) {
        const [argRemoteName = "", argRemoteUrl = ""] = Array.isArray(args) ? args : [];
        let remoteName = argRemoteName;
        let remoteUrl = argRemoteUrl;
        const currentBranch = this.tryReadGitOutput("git rev-parse --abbrev-ref HEAD", repoRoot);
        if (!remoteName && currentBranch) {
            remoteName = this.tryReadGitOutput(`git config branch.${currentBranch}.remote`, repoRoot) || "origin";
        }
        if (!remoteUrl && remoteName) {
            remoteUrl = this.tryReadGitOutput(`git remote get-url ${remoteName}`, repoRoot);
        }
        return {
            remoteName: remoteName || "",
            remoteUrl: remoteUrl || ""
        };
    }

    safeGitRoot(cwd) {
        try {
            return execSync("git rev-parse --show-toplevel", { cwd }).toString().trim();
        } catch {
            return "";
        }
    }

    tryReadGitOutput(command, cwd) {
        try {
            return execSync(command, { cwd }).toString().trim();
        } catch {
            return "";
        }
    }
    
    resolveRepoNotificationConfig(repoName) {
        if (!repoName) {
            return null;
        }
        const forcedRepo = process.env.FEISHU_PUSH_REPO || "";
        if (forcedRepo && repoName !== forcedRepo) {
            return null;
        }
        if (!forcedRepo && !REPO_PUSH_CONFIG[repoName]) {
            return null;
        }
        const override = REPO_PUSH_CONFIG[repoName] || {};
        return {
            chatId: process.env.FEISHU_PUSH_CHAT_ID || DEFAULT_CHAT_ID,
            introText: override.introText || DEFAULT_INTRO_TEXT,
            footerText: override.footerText || DEFAULT_FOOTER_TEXT
        };
    }
}

// 导出执行函数供路由器调用
export const execute = async (args) => {
    const handler = new AutoMergeCommandHandler();
    await handler.execute(args);
};
